﻿using AsmShaderConverter.AsmShader;
using AsmShaderConverter.HlslShader;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows.Forms;

namespace AsmShaderConverter
{
    public partial class Form1 : Form
    {
        private OpenFileDialog ofd;
        private HlslShaderFile hlslVS;
        private HlslShaderFile hlslPS;
        private InformationExportedShaders informationExportedShaders;

        public Form1()
        {
            InitializeComponent();
            //AsmShader.AsmShaderFile file = AsmShader.AsmShaderFile.FromFile(@"C:\Users\tobi\Documents\Visual Studio 2013\Projects\Scenario Editor 2010\Engine\Shaders\C11Undercover\material00.fx");

            // file.SaveToHLSL("c:\\test.hlsl");

            //GetAllLinesWhereMadFunctionIs(Application.StartupPath + "\\Shaders");

            this.ofd = new OpenFileDialog();
            this.ofd.InitialDirectory = @"C:\Users\tobi\Source\Workspaces\wr2sc\Main\Engine\Shaders\C11Undercover";
            //if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            //{
            //    Process.Start("notepad.exe", ofd.FileName);
            //    //using (StreamReader sr = new StreamReader(ofd.FileName))
            //    //{
            //    //    this.richTextBox_LoadedShader.Text = sr.ReadToEnd();
            //    //}
            //}

            //this.richTextBox_LoadedShader.Text = LoadBinShader.LoadBin("test shader 3l diff.bin");

            this.informationExportedShaders = new InformationExportedShaders();
        }

        private void GetAllLinesWhereMadFunctionIs(string folder)
        {
            string[] filenames = Directory.GetFiles(folder, "*.fx");
            List<string> allMethods = new List<string>();
            foreach (var filename in filenames)
            {
                GetMethodParametersInFile(filename, allMethods, "mad ");
            }
            allMethods.Sort();

            StringBuilder sb = new StringBuilder();

            foreach (var item in allMethods)
            {
                sb.AppendLine(item);
            }

            this.richTextBox_VertexShader.Clear();
            this.richTextBox_VertexShader.Text = sb.ToString();
        }

        private void GetMethodParametersInFile(string filename, List<string> allMethods, string functionName)
        {
            StreamReader sr = new StreamReader(filename);
            string line;
            while ((line = sr.ReadLine()) != null)
            {
                line = line.Trim();
                if (line.StartsWith("PixelShader"))
                    break;
                if (line.StartsWith(functionName))
                {
                    string[] values = line.Split(' ');

                    string complete = string.Empty;

                    foreach (var item in values)
                    {
                        complete += item.PadRight(13);
                    }
                    allMethods.Add(complete + Path.GetFileName(filename));
                }
            }
            sr.Close();
            sr.Dispose();
        }

        private enum currentCommentBlock
        {
            none,
            Parameter,
            Register
        }

        private enum ShaderType
        {
            none,
            PixelShader,
            VertexShader
        }

        private void button_Convert_Click(object sender, EventArgs e)
        {
            // replace variables with engine variables.
            Dictionary<string, string> replaceParameterNames = new Dictionary<string, string>();

            replaceParameterNames.Add("WorldViewProjMtx", "worldViewProj");
            replaceParameterNames.Add("WorldViewMtx", "view");
            replaceParameterNames.Add("CameraPos", "g_CameraPosition");

            // Hlsl
            this.hlslVS = new HlslShaderFile();
            hlslVS.AddHeadLines(this.richTextBox_HeaderVertexShader.Text);// "#include \"cobra11syndicate.hlsli\"");
                                                                          // hlslVS.AddHeadLines("#include \"..\\\\standard.hlsli\"");

            hlslVS.SetInputStructName("VS_Input_C11Auto", "vsInAuto");
            hlslVS.SetOutputStructName("PS_Input_C11Auto", "psInAuto");

            this.hlslPS = new HlslShaderFile();
            hlslPS.AddHeadLines(this.richTextBox_HeaderPixelShader.Text);//"#include \"cobra11syndicate.hlsli\"");
            //hlslPS.AddHeadLines("#include \"..\\\\standard.hlsli\"");
            hlslPS.SetInputStructName("PS_Input_C11Auto", "psInAuto");
            hlslPS.SetOutputStructName("float4", "oC0");

            // VertexShader

            hlslVS.AsmShader = new AsmShaderFile(AsmShaderHelper.ShaderType.VertexShader, AsmShaderHelper.ShaderVersion.V5_0);
            var vsAsm = hlslVS.AsmShader;
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslVS.InputStruct.VariableName + ".position", Length = 1, registerName = "v0", typeName = "float" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslVS.InputStruct.VariableName + ".normal", Length = 1, registerName = "v1", typeName = "float" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslVS.InputStruct.VariableName + ".texcoord1", Length = 1, registerName = "v2", typeName = "float" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslVS.InputStruct.VariableName + ".texcoord2", Length = 1, registerName = "v3", typeName = "float" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslVS.InputStruct.VariableName + ".vertexColor", Length = 1, registerName = "v4", typeName = "float" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslVS.InputStruct.VariableName + ".DiffuseColor", Length = 1, registerName = "v5", typeName = "float" });

            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".position", Length = 1, registerName = "o0", typeName = "float4" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".VertexColor", Length = 1, registerName = "o1", typeName = "float4" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".diffuseColor", Length = 1, registerName = "o2", typeName = "float4" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".normal", Length = 1, registerName = "o3", typeName = "float4" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".distanceToCamera", Length = 1, registerName = "o4", typeName = "float4" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".texCoord5", Length = 1, registerName = "o5", typeName = "float4" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".texCoord6", Length = 1, registerName = "o6", typeName = "float4" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".texCoord7", Length = 1, registerName = "o7", typeName = "float4" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".texCoord8", Length = 1, registerName = "o8", typeName = "float4" });
            vsAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".fog", Length = 1, registerName = "o9", typeName = "float4" });

            // PixelShader
            AsmShaderFile psAsm = new AsmShaderFile(AsmShaderHelper.ShaderType.PixelShader, AsmShaderHelper.ShaderVersion.V5_0);
            hlslPS.AsmShader = psAsm;
            psAsm.AddSamplerToTexture(new Sampler2Texture() { samplerName = "s0", textureName = "Tex1", samplerState = EnumSamplerstate.Texture2D });
            psAsm.AddSamplerToTexture(new Sampler2Texture() { samplerName = "s1", textureName = "Tex1Bump", samplerState = EnumSamplerstate.Texture2D });
            psAsm.AddSamplerToTexture(new Sampler2Texture() { samplerName = "s2", textureName = "Tex2", samplerState = EnumSamplerstate.Texture2D });
            psAsm.AddSamplerToTexture(new Sampler2Texture() { samplerName = "s3", textureName = "Tex2Bump", samplerState = EnumSamplerstate.Texture2D });
            psAsm.AddSamplerToTexture(new Sampler2Texture() { samplerName = "s4", textureName = "Tex3", samplerState = EnumSamplerstate.Texture2D });
            psAsm.AddSamplerToTexture(new Sampler2Texture() { samplerName = "s5", textureName = "Tex3Bump", samplerState = EnumSamplerstate.Texture2D });
            psAsm.AddSamplerToTexture(new Sampler2Texture() { samplerName = "s7", textureName = "TexShadowMap", samplerState = EnumSamplerstate.Texture2D });
            psAsm.AddSamplerToTexture(new Sampler2Texture() { samplerName = "s9", textureName = "TexEnvMapTex", samplerState = EnumSamplerstate.TextureCube });
            psAsm.AddSamplerToTexture(new Sampler2Texture() { samplerName = "s10", textureName = "TexDepthTex", samplerState = EnumSamplerstate.Texture2D });
            psAsm.AddSamplerToTexture(new Sampler2Texture() { samplerName = "s15", textureName = "TexDither", samplerState = EnumSamplerstate.Texture2D });

            psAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".VertexColor", Length = 1, registerName = "v0", typeName = "float" });
            psAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".diffuseColor", Length = 1, registerName = "v1", typeName = "float" });
            psAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".normal", Length = 1, registerName = "v2", typeName = "float" });
            psAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".distanceToCamera", Length = 1, registerName = "v3", typeName = "float" });
            psAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".v4", Length = 1, registerName = "v4", typeName = "float" });
            psAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".v5", Length = 1, registerName = "v5", typeName = "float" });
            psAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".v6", Length = 1, registerName = "v6", typeName = "float" });
            psAsm.AddExportRegister(new ExportRegisterNames() { offset = 0, ExportName = hlslPS.InputStruct.VariableName + ".v7", Length = 1, registerName = "vPos", typeName = "float" });

            ConvertasmToHlsl(hlslVS, this.richTextBox_VertexShader.Lines, vsAsm, replaceParameterNames);
            ConvertasmToHlsl(hlslPS, this.richTextBox_PixelShader.Lines, psAsm, replaceParameterNames);

            this.richTextBox_PixelShaderInput.Text = hlslPS.GetInputLinesInput(psAsm, hlslVS);
            this.richTextBox_PixelShaderConverted.Text = hlslPS.CreateShaderCode(psAsm);

            // change type for vertexshader input struct to match the used semantec in the engine.
            foreach (var item in hlslVS.InputStruct.GetRegisters())
            {
                switch(item.semantic)
                {
                    case AsmSemantics.dcl_texcoord:
                    case AsmSemantics.dcl_texcoord0:
                        item.semantic = AsmSemantics.dcl_texcoord1;
                        break;
                    case AsmSemantics.dcl_texcoord1:
                        item.semantic = AsmSemantics.dcl_texcoord2;
                        break;
                }
            }

            this.richTextBox_VertexShaderInput.Text = hlslVS.GetInputLinesInput(vsAsm); // VS_Input and PS_Output
            this.richTextBox_VertexShaderConverted.Text = hlslVS.CreateShaderCode(vsAsm);
            //this.richTextBox_HeaderVertexShader.Text = hlslVS.GetHeaderLines();
        }

        private void ConvertasmToHlsl(HlslShaderFile hlsl, string[] Lines, AsmShaderFile asmShader, Dictionary<string, string> replaceParameterNames)
        {
            currentCommentBlock currentBlock = currentCommentBlock.none;

            for (int j = 0; j < Lines.Length; j++)
            {
                string item = Lines[j].Trim();
                item = Lines[j].Trim();

                string lineOfCode = item;
                if (!item.StartsWith("//"))
                    if (lineOfCode.Contains("//"))
                        item = lineOfCode.Substring(0, lineOfCode.IndexOf("/")).Trim();

                string[] values = item.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                if (values.Length > 0)
                    switch (values[0])
                    {
                        case "ps_2_0":
                        case "ps_3_0":
                            hlsl.ShaderType = HlslShaderTy.PixelShader; break;
                        case "vs_2_0":
                        case "vs_3_0":
                            hlsl.ShaderType = HlslShaderTy.VertexShader; break;
                        case "def":
                            hlsl.AddConstantValues(new AsmShader.ConstantValue(item));
                            break;

                        case "dcl_position":
                        case "dcl_normal":
                        case "dcl_texcoord":
                        case "dcl_texcoord1":
                        case "dcl_texcoord2":
                        case "dcl_texcoord3":
                        case "dcl_texcoord4":
                        case "dcl_texcoord5":
                        case "dcl_texcoord6":
                        case "dcl_texcoord7":
                        case "dcl_texcoord8":
                        case "dcl":
                        case "dcl_color":
                        case "dcl_color1":
                        case "dcl_fog":
                            hlsl.AddInput(new AsmShader.AsmShaderInputOutput(item));
                            break;

                        case "dcl_cube":
                        case "dcl_2d": break;
                        case "mad":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionMad(item));

                            break;

                        case "//": // Kommentar.
                            // Get commentBlock
                            if (values.Length == 2)
                            {
                                if (values[1] == "Parameters:")
                                    currentBlock = currentCommentBlock.Parameter;
                                if (values[1] == "Registers:")
                                    currentBlock = currentCommentBlock.Register;
                            }

                            switch (currentBlock)
                            {
                                case currentCommentBlock.Parameter:
                                    if (values.Length == 3)
                                    {
                                        int arraySize = 1;
                                        string s = values[2].Replace(";", "");
                                        int startSubstring = s.IndexOf('[');
                                        int endSubstring = s.IndexOf(']');
                                        if (startSubstring != -1 && endSubstring != -1)
                                        {
                                            arraySize = Convert.ToInt32(s.Substring(startSubstring + 1, endSubstring - startSubstring - 1));
                                            s = s.Substring(0, startSubstring);
                                        }

                                        string replaceName;
                                        if (replaceParameterNames.TryGetValue(s, out replaceName))
                                            s = replaceName;

                                        asmShader.AddParameter(new AsmParameters() { name = s, type = values[1], ArraySize = arraySize });
                                    }
                                    break;

                                case currentCommentBlock.Register:
                                    if (values.Length == 4
                                        && item != "//   ------------------ ----- ----"
                                        && item != "//   ---------------- ----- ----"
                                        )
                                    {
                                        if (
                                            (values[1] == "Name" &&
                                            values[2] == "Reg" &&
                                            values[3] == "Size") ||

                                            values[3] == "----"
                                            )
                                            continue;

                                        string replaceName;
                                        if (replaceParameterNames.TryGetValue(values[1], out replaceName))
                                            values[1] = replaceName;
                                        try
                                        {
                                            RegisterFromComment register = new RegisterFromComment() { name = values[1], registerName = values[2], size = Convert.ToInt32(values[3]) };
                                            asmShader.AddRegister(register);
                                        }
                                        catch (Exception ex)
                                        {
                                            this.richTextBox_Errors.AppendText(ex.ToString());
                                        }
                                    }

                                    break;
                            }

                            break;

                        case "": // Empty line.
                            break;

                        case "nrm":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionNrm(item));
                            break;

                        case "dp3_sat":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionDot3Sat(item));
                            break;

                        case "add":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionAdd(item));
                            break;

                        case "mul":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionMul(item));
                            break;

                        case "dp2add_pp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionDot2addPP(item));
                            break;

                        case "dp3":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionDot3(item));
                            break;

                        case "rsq":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionRsq(item));
                            break;

                        case "if":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionIf(item));
                            break;

                        case "rcp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionRcp(item));
                            break;

                        case "texld_pp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionTexldr(item, true));
                            break;

                        case "texld":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionTexldr(item, false));
                            break;

                        case "texldp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionTexldp(item));
                            break;

                        case "texkill":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionTexkill(item));
                            break;

                        case "endif":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionEndif(item));
                            break;

                        case "mul_pp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionMulPP(item));
                            break;

                        case "mul_sat":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionMulSat(item));
                            break;

                        case "dp3_pp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionDot3PP(item));
                            break;

                        case "add_sat_pp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionAddSatPP(item));
                            break;

                        case "mad_sat":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionMadSat(item));
                            break;

                        case "log":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionLog(item));
                            break;

                        case "max":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionMax(item));
                            break;

                        case "min":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionMin(item));
                            break;

                        case "frc":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionFrc(item));
                            break;

                        case "dp4":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionDot4(item));
                            break;

                        case "mov_pp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionMov(item, true, false));
                            break;

                        case "mov_sat":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionMov(item, false, true));
                            break;

                        case "mov":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionMov(item, false, false));
                            break;

                        case "cmp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionCmp(item));
                            break;

                        case "else":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionElse(item));
                            break;

                        case "lrp_pp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionLrp(item, true));
                            break;

                        case "lrp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionLrp(item));
                            break;

                        case "mad_pp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionMad(item, true));
                            break;

                        case "dp3_sat_pp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionDot3Sat(item, true));
                            break;

                        case "add_pp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionAdd(item, true));
                            break;

                        case "exp":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionExp(item));
                            break;

                        case "pow":
                            hlsl.AddFunction(new AsmFunctions.AsmFunctionPow(item));
                            break;

                        default:
                            string value = values[0];
                            //MessageBox.Show("Kann Zeile nicht auswerten: " + item);
                            // throw new Exception("Kann Zeile nicht umwnadeln");
                            if (item == "{" ||
                                item == "}")
                                continue;
                            richTextBox_Errors.AppendText(item + "\r\n");
                            break;
                    }
            }
        }

        private void button_Save_Click(object sender, EventArgs e)
        {
            string filenameFormat = this.textBox_materialName.Text + "{0}.hlsl";
            if (this.hlslPS != null)
                this.hlslPS.SaveToFile(string.Format(filenameFormat, "PS"), this.hlslVS);
            if (this.hlslVS != null)
                this.hlslVS.SaveToFile(string.Format(filenameFormat, "VS"), null);

            // Get used Sampler
            this.informationExportedShaders.AddShaderInformations(this.textBox_materialName.Text, this.hlslPS.GetUsedSampler());

            // Rename Input struct lines from VertexShader
            StreamReader sr = new StreamReader(string.Format(filenameFormat, "VS"));
            string linesVS = sr.ReadToEnd();
            sr.Close();

            linesVS = linesVS.Replace("float4 DiffuseColor : color1", "float4 DiffuseColor : color0");
            linesVS = linesVS.Replace("float4 vertexColor : color", "float4 vertexColor : color1");

            StreamWriter sw2 = new StreamWriter(string.Format(filenameFormat, "VS"));
            sw2.Write(linesVS);
            sw2.Close();

            if (this.richTextBox_Errors.Lines.Length > 0)
            {
                using (StreamWriter sw = new StreamWriter(this.textBox_materialName.Text + "_Error.txt"))
                {
                    foreach (var item in this.richTextBox_Errors.Lines)
                    {
                        sw.WriteLine(item);
                    }
                }
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "Fx-File (*.fx)|*.fx";

            if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                string fileName = ofd.FileName;

                InstantConvert(fileName);
            }
        }

        private void InstantConvert(string fileName)
        {
            string linesPS;
            string linesVS;

            LoadFxFile.LoadFromFile(fileName, out linesPS, out linesVS);

            this.richTextBox_PixelShader.ResetText();
            this.richTextBox_PixelShader.Text = linesPS;

            this.richTextBox_VertexShader.ResetText();
            this.richTextBox_VertexShader.Text = linesVS;

            this.textBox_materialName.Text = Path.GetFileNameWithoutExtension(fileName);

            this.richTextBox_Errors.ResetText();

            button_Convert_Click(null, null);
            button_Save_Click(null, null);
        }

        private void button3_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            fbd.SelectedPath = @"C:\Users\tobi\Source\Workspaces\wr2sc\Main\Engine\Shaders\C11Undercover";

            if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                this.informationExportedShaders.Clear();
                var files = Directory.GetFiles(fbd.SelectedPath, "*.fx");

                foreach (var filename in files)
                {
                    InstantConvert(filename);
                }
                this.informationExportedShaders.SaveToFile("shaderInformation.txt");
                Process.Start("Explorer.exe", Application.StartupPath);
                Process.Start("Explorer.exe", @"C:\Users\tobi\Source\Workspaces\wr2sc\Main\ShaderCreator\Shaders\C11Undercover");
            }
        }
    }
}